package com.demo.sso.sdk.service;

import com.demo.sso.sdk.config.SsoSdkProperties;
import com.demo.sso.sdk.model.SsoAccessToken;
import com.demo.sso.sdk.model.SsoApiResponse;
import com.demo.sso.sdk.model.SsoUserInfo;
import com.demo.sso.sdk.util.HttpUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SSO客户端核心服务类
 * 
 * @author SpringCloud团队
 * @version 1.0.0
 * @since 2025-01-01
 */
@Service
public class SsoClientService {

    private static final Logger logger = LoggerFactory.getLogger(SsoClientService.class);

    private final SsoSdkProperties properties;
    private final HttpUtils httpUtils;
    
    // 用户信息缓存，key为accessToken，value为用户信息
    private final Map<String, SsoUserInfo> userInfoCache = new ConcurrentHashMap<>();
    
    // 缓存过期时间，key为accessToken，value为过期时间戳
    private final Map<String, Long> cacheExpireTime = new ConcurrentHashMap<>();

    // Session中存储的key常量
    private static final String SESSION_ACCESS_TOKEN = "sso_access_token";
    private static final String SESSION_USER_INFO = "sso_user_info";
    private static final String SESSION_STATE = "sso_state";

    public SsoClientService(SsoSdkProperties properties) {
        this.properties = properties;
        this.httpUtils = new HttpUtils(
                properties.getConnectTimeout(),
                properties.getReadTimeout(),
                properties.getIgnoreSsl()
        );
    }

    /**
     * 生成SSO登录URL
     * 
     * @param request HTTP请求对象
     * @return SSO登录URL
     */
    public String generateLoginUrl(HttpServletRequest request) {
        if (!properties.isValid()) {
            throw new IllegalStateException("SSO SDK configuration is invalid");
        }

        // 生成state参数并保存到session
        String state = UUID.randomUUID().toString();
        HttpSession session = request.getSession();
        session.setAttribute(SESSION_STATE, state);

        // 构建授权URL参数
        Map<String, String> params = new HashMap<>();
        //sso配置的clientId
        params.put("clientId", properties.getClientId());
        //重定向url
        params.put("redirectUri", properties.getRedirectUri());
        params.put("responseType", "code");
        params.put("state", state);
        //sso登录平台
        String authorizeUrl = properties.getServerUrl() + "/oauth2/authorize";
        String loginUrl = HttpUtils.buildGetUrl(authorizeUrl, params);
        
        logger.info("Generated SSO login URL: {}", loginUrl);
        return loginUrl;
    }

    /**
     * 处理SSO回调
     * 
     * @param code 授权码
     * @param state state参数
     * @param request HTTP请求对象
     * @return 用户信息，失败返回null
     * @throws IOException IO异常
     */
    public SsoUserInfo handleCallback(String code, String state, HttpServletRequest request) throws IOException {
        logger.info("Handling SSO callback with code: {}, state: {}", code, state);

        // 验证state参数
        HttpSession session = request.getSession();
        String savedState = (String) session.getAttribute(SESSION_STATE);
        if (savedState == null || !savedState.equals(state)) {
            logger.error("Invalid state parameter. Expected: {}, Actual: {}", savedState, state);
            throw new IllegalArgumentException("Invalid state parameter");
        }

        // 清除state参数
        session.removeAttribute(SESSION_STATE);

        // 使用授权码换取访问令牌
        SsoAccessToken accessToken = exchangeCodeForToken(code);
        if (accessToken == null) {
            logger.error("Failed to exchange code for access token");
            return null;
        }

        // 获取用户信息
        SsoUserInfo userInfo = getUserInfo(accessToken.getAccessToken());
        if (userInfo == null) {
            logger.error("Failed to get user info with access token");
            return null;
        }

        // 保存到session
        session.setAttribute(SESSION_ACCESS_TOKEN, accessToken);
        session.setAttribute(SESSION_USER_INFO, userInfo);

        logger.info("SSO login successful for user: {}", userInfo.getUsername());
        return userInfo;
    }

    /**
     * 使用授权码换取访问令牌
     * 
     * @param code 授权码
     * @return 访问令牌
     * @throws IOException IO异常
     */
    private SsoAccessToken exchangeCodeForToken(String code) throws IOException {
        logger.debug("Exchanging code for access token: {}", code);

        Map<String, String> params = new HashMap<>();
        params.put("grantType", "authorization_code");
        params.put("code", code);
        params.put("clientId", properties.getClientId());
        params.put("clientSecret", properties.getClientSecret());

        String tokenUrl = properties.getServerUrl() + "/oauth2/token";
        String requestUrl = HttpUtils.buildGetUrl(tokenUrl, params);

        try {
            String response = httpUtils.doGet(requestUrl);
            SsoApiResponse<SsoAccessToken> apiResponse = HttpUtils.parseResponse(response, SsoAccessToken.class);
            
            if (apiResponse.isSuccess()) {
                logger.debug("Successfully exchanged code for access token");
                return apiResponse.getData();
            } else {
                logger.error("Failed to exchange code for token: {}", apiResponse.getMsg());
                return null;
            }
        } catch (IOException e) {
            logger.error("Error exchanging code for token", e);
            throw e;
        }
    }

    /**
     * 使用访问令牌获取用户信息
     * 
     * @param accessToken 访问令牌
     * @return 用户信息
     * @throws IOException IO异常
     */
    public SsoUserInfo getUserInfo(String accessToken) throws IOException {
        if (accessToken == null || accessToken.trim().isEmpty()) {
            return null;
        }

        // 检查缓存
        SsoUserInfo cachedUserInfo = getUserInfoFromCache(accessToken);
        if (cachedUserInfo != null) {
            logger.debug("Retrieved user info from cache");
            return cachedUserInfo;
        }

        logger.debug("Getting user info with access token");

        Map<String, String> params = new HashMap<>();
        params.put("accessToken", accessToken);

        String userInfoUrl = properties.getServerUrl() + "/oauth2/userinfo";
        String requestUrl = HttpUtils.buildGetUrl(userInfoUrl, params);

        try {
            String response = httpUtils.doGet(requestUrl);
            SsoApiResponse<SsoUserInfo> apiResponse = HttpUtils.parseResponse(response, SsoUserInfo.class);
            
            if (apiResponse.isSuccess()) {
                SsoUserInfo userInfo = apiResponse.getData();
                // 缓存用户信息
                cacheUserInfo(accessToken, userInfo);
                logger.debug("Successfully retrieved user info for: {}", userInfo.getUsername());
                return userInfo;
            } else {
                logger.error("Failed to get user info: {}", apiResponse.getMsg());
                return null;
            }
        } catch (IOException e) {
            logger.error("Error getting user info", e);
            throw e;
        }
    }

    /**
     * 从session获取当前用户信息
     * 
     * @param request HTTP请求对象
     * @return 用户信息，未登录返回null
     */
    public SsoUserInfo getCurrentUser(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }
        return (SsoUserInfo) session.getAttribute(SESSION_USER_INFO);
    }

    /**
     * 从session获取访问令牌
     * 
     * @param request HTTP请求对象
     * @return 访问令牌，未登录返回null
     */
    public SsoAccessToken getCurrentAccessToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
            return null;
        }
        return (SsoAccessToken) session.getAttribute(SESSION_ACCESS_TOKEN);
    }

    /**
     * 检查用户是否已登录
     * 
     * @param request HTTP请求对象
     * @return true-已登录，false-未登录
     */
    public boolean isLoggedIn(HttpServletRequest request) {
        SsoUserInfo userInfo = getCurrentUser(request);
        if (userInfo == null) {
            return false;
        }

        // 检查访问令牌是否过期
        SsoAccessToken accessToken = getCurrentAccessToken(request);
        if (accessToken == null || accessToken.isExpired()) {
            // 令牌过期，清除session
            logout(request);
            return false;
        }

        return true;
    }

    /**
     * 用户登出
     * 
     * @param request HTTP请求对象
     */
    public void logout(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 从缓存中移除用户信息
            SsoAccessToken accessToken = (SsoAccessToken) session.getAttribute(SESSION_ACCESS_TOKEN);
            if (accessToken != null) {
                removeUserInfoFromCache(accessToken.getAccessToken());
            }

            // 清除session
            session.removeAttribute(SESSION_ACCESS_TOKEN);
            session.removeAttribute(SESSION_USER_INFO);
            session.removeAttribute(SESSION_STATE);
            
            logger.info("User logged out successfully");
        }
    }

    /**
     * 生成登出URL
     * 
     * @return 登出URL
     */
    public String generateLogoutUrl() {
        return properties.getServerUrl() + "/auth/logout";
    }

    /**
     * 缓存用户信息
     * 
     * @param accessToken 访问令牌
     * @param userInfo 用户信息
     */
    private void cacheUserInfo(String accessToken, SsoUserInfo userInfo) {
        if (properties.getUserInfoCacheTimeout() <= 0) {
            return;
        }

        userInfoCache.put(accessToken, userInfo);
        long expireTime = System.currentTimeMillis() + properties.getUserInfoCacheTimeout() * 1000L;
        cacheExpireTime.put(accessToken, expireTime);
        
        logger.debug("Cached user info for token: {}", accessToken.substring(0, Math.min(accessToken.length(), 10)) + "...");
    }

    /**
     * 从缓存获取用户信息
     * 
     * @param accessToken 访问令牌
     * @return 用户信息，缓存不存在或过期返回null
     */
    private SsoUserInfo getUserInfoFromCache(String accessToken) {
        Long expireTime = cacheExpireTime.get(accessToken);
        if (expireTime == null || System.currentTimeMillis() > expireTime) {
            // 缓存过期，清除
            removeUserInfoFromCache(accessToken);
            return null;
        }
        
        return userInfoCache.get(accessToken);
    }

    /**
     * 从缓存移除用户信息
     * 
     * @param accessToken 访问令牌
     */
    private void removeUserInfoFromCache(String accessToken) {
        if (accessToken != null) {
            userInfoCache.remove(accessToken);
            cacheExpireTime.remove(accessToken);
        }
    }

    /**
     * 清理过期的缓存
     * 定期调用此方法清理过期缓存
     */
    public void cleanExpiredCache() {
        long currentTime = System.currentTimeMillis();
        cacheExpireTime.entrySet().removeIf(entry -> {
            if (currentTime > entry.getValue()) {
                userInfoCache.remove(entry.getKey());
                return true;
            }
            return false;
        });
    }
}